/*
 * Galaxium Messenger
 * Copyright (C) 2003-2007 Philippe Durand <draekz@gmail.com>
 * Copyright (C) 2007 Paul Burton <paulburton89@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#define DEBUGSPLIT

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

using Anculus.Core;

using Galaxium.Core;
using Galaxium.Protocol;

namespace Galaxium.Gui.GtkGui
{
	public static class PangoUtility
	{
		static Regex _regexElements = new Regex (@"<([A-Za-z][A-Za-z0-9]*)[> ]", RegexOptions.Compiled);
		
		//TODO: we need a way to remove old cached results that won't be used anymore
		static Dictionary<string, List<ITextChunk>> _cache = new Dictionary<string, List<ITextChunk>> ();
		
		public static List<ITextChunk> SplitMarkup (string markup, IProtocol protocol, IEntity entity)
		{
			// Check the cache
			
			if (_cache.ContainsKey (markup))
				return _cache[markup];
			
			int pos = 0;
			string tags = string.Empty;
			List<ITextChunk> chunks = new List<ITextChunk> ();
			
#if DEBUGWHILE
			int iterations = 0;
#endif
			
			while (pos < markup.Length)
			{
#if DEBUGWHILE
				iterations++;
				
				if (iterations > 100)
					Log.Warn ("Iteration {0}, pos {1} markup '{2}'", iterations, pos, markup);
#endif
				
				// Find the next xml element open or close node
				
				int nextStart = markup.IndexOf ("<", pos);
				int nextEnd = (nextStart < 0) ? -1 : markup.IndexOf (">", nextStart);
				bool isClose = (markup.Length > nextStart + 1) && (markup[nextStart + 1] == '/');
				
				if ((nextStart < 0) || (nextStart > pos))
				{
					// Either there's no more xml elements or there's text before the next element node
					// Parse the text with MessageUtility
					
					string text = (nextStart < 0) ? markup.Substring (pos) : markup.Substring (pos, nextStart - pos);
					List<ITextChunk> msgChunks = MessageUtility.SplitMessage (XmlUtility.GetDecodedString (text), new TextStyle (), protocol, entity, null);
					
					foreach (ITextChunk chunk in msgChunks)
					{
						// Emoticons don't need escaping because they don't get parsed as pango markup
						// All other chunks need XML encoding and wrapping in the correct markup
						
						if (chunk.Type == TextChunkType.Emoticon)
							chunks.Add (chunk);
						else
							chunks.Add (new TextChunk (chunk.Style, chunk.Type, WrapText (XmlUtility.GetEncodedString (chunk.Text), tags, chunk.Style)));
					}
					
					pos += text.Length;
				}
				
				if (nextStart >= 0)
				{
					if (isClose) // Remove the last opening node
						tags = tags.Substring (0, tags.LastIndexOf ("<"));
					else // Add new opening node
						tags += markup.Substring (nextStart, (nextEnd - nextStart) + 1);
					
					pos = nextEnd + 1;
				}
			}
			
			// Cache the result
			_cache.Add (markup, chunks);
			
			return chunks;
		}
		
		static string WrapText (string text, string tags, ITextStyle style)
		{
			StringBuilder sb = new StringBuilder ();
			sb.Append ("<span ");
			
			if (style.ForeColor > 0)
				sb.AppendFormat ("foreground=\"{0}\" ", RGBColor (style.ForeColor));
			if (style.BackColor > 0)
				sb.AppendFormat ("background=\"{0}\" ", RGBColor (style.BackColor));
			
			if (style.Bold)
				sb.Append ("weight=\"bold\" ");
			if (style.Italic)
				sb.Append ("style=\"italic\" ");
			if (style.Underline)
				sb.Append ("underline=\"single\" ");
			if (style.Strikethrough)
				sb.Append ("strikethrough=\"true\" ");
			
			sb.Append (">");
			sb.Append (tags);
			sb.Append (text);
			
			// Append closing nodes by finding the element names with the regexp
			
			MatchCollection matches = _regexElements.Matches (tags);
			for (int i = matches.Count - 1; i >= 0; i--)
				sb.AppendFormat ("</{0}>", matches[i].Groups[1].Value);
			
			sb.Append ("</span>");
			
			return sb.ToString ();
		}
		
		static string RGBColor (int color)
		{
			return string.Format ("#{0:x2}{1:x2}{2:x2}", (byte)((color & 0xFF0000) >> 16), (byte)((color & 0xFF00) >> 8), (byte)(color & 0xFF));
		}
		
		public static void ClearMarkupCache ()
		{
			_cache.Clear ();
		}
	}
}
